<?php
/**
 * index.php
 *
 *   This file is the main "honeypot client". It will collect the request header information and
 * pass it to the collection server.
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

// automatically generated by the SVN repository
$whVersion = '$Rev: 33 $';
$whVersion = '0.1.r' . preg_replace('/^.*(\d+).*$/', '\\1', $whVersion);
// we don't care if the client disconnected - still want to log the request
ignore_user_abort(TRUE);
$aConfig = array(
	'template_basenames' => array(),
);

//********************************************************
//* 	User Editable portion
//********************************************************

/**
 * Let me know where to find the templates and configuration files and where to write logfiles 
 * Change this line to point to the correct directory/folder
 * The directory must be outside of the document root, or else the honeypot
 * will refuse to start - for security reasons
 */

//absolute path must be used here, because when running in CLI mode we don't have access to
//the document root variable
$sBaseDir     = "D:/A/webhoneypot/html/" . '/../honeypot_data';
//the filename for the logfile - can contain php.date escapes  
$sLogfileName = 'honeypot-%Y-%m.log';

//********************************************************
//* 	User Editable portion ENDS
//********************************************************

/* Do not edit the following parameters unless you know what you are doing */

// use the absolute basedir to make output easier to understand
$sBaseDir = realpath($sBaseDir);
$aConfig['loglevel'] = 5;
$aConfig['logfile']  = $sBaseDir . '/logs/' . strftime($sLogfileName);
$aConfig['url']      = 'http://localhost/submit.php';
$aConfig['updateurl']= 'http://localhost/update.php';

// do some checking of the configuration parameters and see if they conform to our expectations

if (!is_dir($sBaseDir))
	die_on_error('Base directory not found!');
if (!is_above_document_root($sBaseDir))
	die_on_error('Base directory must be above document root!');
if (!is_dir($sBaseDir . '/templates'))
	die_on_error('Templates directory not found!');
if (!is_dir($sBaseDir . '/logs'))
	die_on_error('logs directory not found!');
if (!file_exists($sBaseDir . '/config.txt'))
	die_on_error('Main configuration file not found!');

// load the settings before going any further, since settings from there
// may override the specified configuration settings
$aTemplateLookup = array();
read_config("$sBaseDir/config.txt", 'global');
read_config("$sBaseDir/config.local", 'local');

if (!isset($aConfig['restrict_templates']))
	$aConfig['restrict_templates'] = 0;

if (!is_file_in_directory($aConfig['logfile'], $sBaseDir . '/logs/'))
	die_on_error('Logfile is not in expected directory!');
if (!is_file_writable($aConfig['logfile']))
	die_on_error('Logfile is not writeable!');
if (!isset($aConfig['url']))
	die_on_error('Submission URL not specified!');
if (!isset($aConfig['updateurl']))
	die_on_error('Update URL not specified!');
if (!isset($aConfig['userid']) || !isset($aConfig['password']))
	die_on_error('Userid or Password not set! Please set them in config.local!');

logdata("Configuration: user     : {$aConfig['userid']}",8);
logdata("Configuration: password : {$aConfig['password']}",9);
logdata("Configuration: posturl  : {$aConfig['url']}",5);
logdata("Configuration: updateurl: {$aConfig['updateurl']}",5);
$sTemplateDir="$sBaseDir/templates";

require_once "$sBaseDir/common_functions.php"; 

if ('cli' == php_sapi_name()) {
	// we are launched from the command line, see what we should do
	if (in_array('--update', $argv)) {
		// we use the logs directory because it should be writeable
		$sLogsDirectory = $sBaseDir . '/logs';
		if (!aquire_lock($sLogsDirectory . '/update.lock'))
			die_on_error('Failed to aquire lockfile for update!');
				
		// send the list to the update server and get back the diff
		echo "Sending request to update server...\n";		
		$sUpdateResponse = send_post_request($aConfig['updateurl'],
			'localfiles=' . urlencode(calculate_file_hashes($sBaseDir)));
		if (!$sUpdateResponse)			
			die_on_error('Failed to get response from update server!');				
		//verify the response received 		
		if (!preg_match('/[a-f0-9]{40}$/D', $sUpdateResponse, $aMatches))
			die_on_error('Server response did not contain final hash!');
		$sSHA1 = $aMatches[0];		
		$sUpdateResponse = substr($sUpdateResponse, 0, strlen($sUpdateResponse) - 40);
		$sLastNonce = get_nonce(false);
		$sExpectedSHA1 = sha1($sLastNonce . $aConfig['userid'] . $aConfig['password'] . $sUpdateResponse);
		if ($sSHA1 != $sExpectedSHA1)
			die_on_error('Server response contained wrong final hash!');
			
		$sUpdateResponse = gzuncompress($sUpdateResponse);
		if (!$sUpdateResponse)
			die_on_error('Failed to uncompress data from server!');
		echo "Processing received data...\n";
		$sLastLine = null; // we need a one-line lookahead		
		while ('' != $sUpdateResponse) {
			$sFileLine = extract_line($sUpdateResponse);
			
			// end of file marker
			if ('DONE!' == $sFileLine) break;
			
			if (is_null($sLastLine)) {
				// first line
				$sLastLine = $sFileLine;
			}
			elseif (preg_match('/([-+][DF]?) (.*)/', $sLastLine, $aMatches)) {
				// file/dir operation
				$sOp       = $aMatches[1];
				$sFilename = validate_filename_for_update($aMatches[2]); // do validations on the filename				
				if (!$sFilename)
					die_on_error("FATAL: Invalid filename received for update: \"{$aMatches[2]}\"");

				if ('-' == $sOp) {
					echo "- $sFilename\n";
					if (!rmdir_recursive($sFilename))
						die_on_error("FATAL: failed to delete template file/directory \"$sFilename\" during update!");										
				}
				elseif ('+' == $sOp[0]) {
					// create file / directory
					echo "+ $sFilename\n";
					if ('F' == $sOp[1]) {
						$sDirname = dirname($sFilename);
						$sFilename = basename($sFilename);					
					}
					else {
						$sDirname = $sFilename;
						$sFilename = '';
					}
					
					// make the full directory - recursive mkdir only available as of PHP 5.0.0
					$sFullDir = '';
					foreach(preg_split("/[\\/\\\\]+/", $sDirname) as $sDirPart) {
						if ('' != $sFullDir) $sFullDir .= '/';
						$sFullDir .= $sDirPart;
						if (is_dir($sFullDir)) continue;
						if (!mkdir($sFullDir, 0644))
							die_on_error("FATAL: Failed to create directory \"$sFullDir\" during update!");
					}
					
					if ('' != $sFilename) {
						// open the file with X to avoid overwriting existing files if somehow we have
						// an invalid path (hopefully the other checks prevented this)
						$fp = fopen($sDirname . '/' . $sFilename, 'xb');
						if (!$fp || !fwrite($fp, $sFileLine))
							die_on_error("FATAL: Failed to write to file \"$sFilename\" in directory \"$sDirname\" during update!");
						fclose($fp);
						$sFileLine = '';						
					}
				}				
			}
			
			$sLastLine = $sFileLine;
		}
		echo "Done!\n";		
	}
	else {
		$sScriptName = basename($argv[0]);
		echo "Usage: $sScriptName [--update]\n"
			. "  --update - updates the templates from SANS\n";
	}
}
elseif (!defined('TESTING')) {
	// we have been called from the web and are not in testing mode
	
	/* to ease install, we do not use any of the fileinfo mime detection.
	 instead, we use this little hash to lookup file types */
	$aMimeCheat=array(
		'gif'  => 'image/gif',
	  'jpg'  => 'image/jpeg',
	  'png'  => 'image/png',
	  'html' => 'text/html',
	  'htm'  => 'text/html',
	  'php'  => 'text/html',
	  'txt'  => 'text/plain',
		'aspx' => 'text/html',
	);

	$sUrl = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : $_SERVER['QUERY_STRING'];	
	// if we use the index.php/foo/bar/baz method of invocation, remove ourselves from the list
	if (0 == strncmp($sUrl, $_SERVER['SCRIPT_NAME'], strlen($_SERVER['SCRIPT_NAME'])))
		 $sUrl = substr($sUrl, strlen($_SERVER['SCRIPT_NAME']));		

	if (0 === strpos($sUrl, '/?') && isset($_GET['sitemap'])) {
		logdata("Delivering sitemap", 2);		
		
		// calculate the base url
		$sBaseUrl = (('on' == @$_SERVER['HTTPS']) ? 'https://' : 'http://') . $_SERVER['HTTP_HOST']
			. str_replace("\\", '/', dirname($_SERVER['SCRIPT_NAME']));		
						
		// create a list of possible rx's for each template
		$aTemplateRxs = array();
		foreach(array('local', 'global') as $sPrefix) {
			foreach ($aTemplateLookup[$sPrefix] as $aRule) {
				$iTempl = $aRule['template'];
				$sRx    = $aRule['rx'];
				if (!isset($aTemplateRxs[$iTempl])) $aTemplateRxs[$iTempl] = array();
				$aTemplateRxs[$iTempl][] = $sRx;
			}
		}
			
		// construct the sitemap - validate the provided URLs
		$aUrls = array();
		foreach ($aConfig['template_basenames'] as $iTid => $sPath) {
			$sSitemapUrl = $sBaseUrl . $sPath;
			if (!isset($aTemplateRxs[$iTid]))
				die_on_error("No RX defined for template $iTid");
			$bTemplateRxMatch = false;
			foreach ($aTemplateRxs[$iTid] as $sRx)
				if (preg_match($sRx, $sSitemapUrl)) {
					$bTemplateRxMatch = true;
					break;
				}
			if (!$bTemplateRxMatch)
				die_on_error("The generated URL \"$sSitemapUrl\" doesn't match any of the "
					. "regular expressions for the specified template ($iTid)!");
			$aUrls[] = $sSitemapUrl;
		}
		
		if ('xml' == $_GET['sitemap']) {
			header('Content-Type: text/xml');
			print '<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" '
				. 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 '
				. 'http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">';
			foreach ($aUrls as $sUrl) 
				print "<url>\n"
					. "  <loc>" . htmlentitieS($sUrl) . "</loc>\n"
					. "  <priority>1.00</priority>\n"
					. "  <lastmod>" . strftime('%Y-%m-%dT%H:%M:%S+00:00') . "</lastmod>"
					. "  <changefreq>daily</changefreq>\n"
					. "</url>\n";
			print "</urlset>";
		}
		elseif ('ror' == $_GET['sitemap']) {
			header('Content-Type: text/xml');
			print '<?xml version="1.0" encoding="UTF-8"?>';
			print '<rss version="2.0" xmlns:ror="http://rorweb.com/0.1/" ><channel>';
  		print '<title>ROR Sitemap for ' . htmlentities($sBaseUrl) . '</title>';
  		print '<link>' . htmlentities($sBaseUrl) . '</link>';
  		foreach ($aUrls as $sUrl) 
  			print "<item>\n"
  				. "  <link>" . htmlentities($sUrl) . "</link>\n"
     			. "  <title>Link to " . htmlentities($sUrl) . "</title>\n"
     			. "  <ror:updatePeriod>daily</ror:updatePeriod>\n"
     			. "  <ror:sortOrder>0</ror:sortOrder>\n"
     			. "  <ror:resourceOf>sitemap</ror:resourceOf>\n"
     			. "</item>\n";
  		print '</channel></rss>';
		}
		else {
			print "<html><body><h1>It works!</h1><div style='display: none'>";
			foreach ($aUrls as $sUrl) print "<a href='" . htmlentities($sUrl) 
				. "'>" . htmlentities($sUrl) . "</a>";
			print "</div></body></html>";			
		}
		
		$iTemplateId = 3;
	}
	else {
		/* deliver the template to the client */
		$iTemplateId = deliver_template($sUrl);
	}
	
	//make sure that from here on we don't output anything to the client...
	flush();	
	ob_start('ob_no_output');
	logdata('Debug: Template delivery done');
	// for template 0 we don't log anything
	if (0 == $iTemplateId) exit;
	
	/* collect the request */
	$sRequest=collect_request();
	logdata("Debug: Done collecting request. Size=".strlen($sRequest)." Bytes",8);
	
	/* posting the request to the collection site */
	$sResult=post_result($sRequest);
	logdata("Debug: Post result = $sResult", 8);	
}

/**
 * logdata function: This function will write data to the log file.
 *
 * @Param $sMsg string the message that will be written to the file.
 * @Param $nLevel int  the log level. Higher log levels are used for
 *                     debugging.
 *
 * A timestamp and the IP address of the client is added automatically.
 *
 */
function logdata($sMsg,$nLevel=1) {
  global $aConfig;
  if (@$aConfig['loglevel'] <= $nLevel) return true;
  
  // escape the special characters from the log message to avoid injection attacks
  $sMsg = preg_replace_callback(
  	'/[^\x20-\x7E]/',
  	create_function(
  		'$matches',
  		'return sprintf(\'\x%02x\', ord($matches[0]));'   	
  	),
  	$sMsg);
  $sMsg = strftime('%Y-%m-%d %H:%M:%s %z') . ' ' . $_SERVER['REMOTE_ADDR'] . ' '
  	. $sMsg . "\n";
  
  return error_log($sMsg, 3, @$aConfig['logfile']);
}

function find_template($sURI, $sType) {
	global $aTemplateLookup;	
	
	$iLastMatchLen = 0; //the length of the last match so that
		//we can find the most specific expression
	$iTemplate = 0; $iStatusCode = 0;
	// the filename portion of the request - this will determine which
	// file to serve up from the template directory
	$sFilename = '';
	foreach ($aTemplateLookup[$sType] as $candidate) {
		if (!preg_match($candidate['rx'], $sURI, $aMatches, PREG_OFFSET_CAPTURE)) continue;
		$aMatch = array_pop($aMatches);
		logdata("Debug: Match {$candidate['rx']} in {$sURI}",9);
		if (strlen($aMatch[0]) > $iLastMatchLen) {
			logdata("Debug: was more specific than the previous one",9);
			$iLastMatchLen = strlen($aMatch[0]);
			$iStatusCode   = $candidate['status_code'];
			$iTemplate     = $candidate['template'];
			$sFilename     = substr($sURI, $aMatch[1]);
			// remove GET parameters from the filename
			$sFilename = preg_replace('/\?.*/sD', '', $sFilename);
		}
	}

	if (0 == $iLastMatchLen) return null;
	
	if ('headers.txt' == strtolower($sFilename))
		$sFilename = 'index.html';
	
	return array(
		'status_code' => $iStatusCode,
		'template'    => $iTemplate,
		'filename'    => $sFilename,
	);
}

/**
 * deliver template
 * 
 * @Param $sURI request URI. Used to lookup the right template.
 */

function deliver_template($sURI) {
	global $aConfig;
  global $aTemplateLookup;
  global $sTemplateDir;
  global $aMimeCheat;
  
  $aTemplate = find_template($sURI, 'local');
  if (is_null($aTemplate))
  	$aTemplate = find_template($sURI, 'global');
  	
  /* if no match was found, we use the default template ('1') */
  if (is_null($aTemplate))
    $aTemplate = array(
			'status_code' => 404,
			'template'    => 1,   
    	'filename'    => '',
    );

  logdata("Debug: Using template # {$aTemplate['template']}",8);
  
  // try to construct the final filename in a safe manner. 
  // the important problem: avoid path traversal
  $sTemplateDirL = $sTemplateDir . '/' . $aTemplate['template'];
  $sTemplatefile = realpath($sTemplateDirL . '/' . $aTemplate['filename']);
  if (is_above_directory($sTemplatefile, $sTemplateDirL))
  	$sTemplatefile = $sTemplateDirL . '/';
  // for directories - we request the index.html from the given directory
  if ('' == basename($sTemplatefile)) $sTemplatefile .= '/index.html';    
  
  if (!file_exists($sTemplatefile)) {
  	// first, try index.html from the diven directory
  	$sProposed = dirname($sTemplatefile) . '/index.html';
  	if (file_exists($sProposed)) {
  		$sTemplatefile = $sProposed;
  		logdata("Debug: can not find $sTemplatefile using index",8);
  	}
  	// next, we fall back to template 1 (404) 
  	else {
  		logdata("Debug: can not find index using default",8);
  		$aTemplate['status_code'] = 404;
  		$sTemplatefile = $sTemplateDir . '/1/index.html';
  	}
  }
  
  if (0 != $aConfig['restrict_templates']) {
  	// disable templates based on client/server IP  	
  	if (1 == $aConfig['restrict_templates']) $sIP = $_SERVER['SERVER_ADDR'];
  	else $sIP = $_SERVER['REMOTE_ADDR'];
  	$iFilter = 10 + array_sum(explode('.', $sIP)) % 10;  	

  	if (0 == ($aTemplate['template'] % $iFilter))
  		// we don't want to serve this template to this client...
	    $aTemplate = array(
				'status_code' => 404,
				'template'    => 1,   
	    	'filename'    => '',
	    );  	
  }
  logdata("Debug: using file $sTemplatefile",8); 

  /* Next, we get the extension, and sent a mime type based on the extension. */
  $sContentType = 'text/html';
  if (preg_match("/\.([^\.]+)$/", $sTemplatefile, $aMatch)) {
  	$sExt=$aMatch[1];
  	if (array_key_exists($sExt,$aMimeCheat)) {
  		logdata("Debug: setting content type to {$aMimeCheat[$sExt]}",8);
      header("Content-Type: {$aMimeCheat[$sExt]}");  		
  	}
  	else {
  		logdata("Info: no content type found for $sExt",3);
  	}
  }
  else {
  	logdata("Debug: no extension found",6);
  }

  /* read the file */
  $sTemplate = @file_get_contents($sTemplatefile, FILE_BINARY);  
  if (!$sTemplate) {
  	//we failed to read the template file, make a last ditch effort to deliver
  	//something to the client - hopefully the logger part will be able to log
  	//the request  	
  	logdata("ERROR: can not open $sTemplate",1);
  	header('HTTP/1.0 404 Not Found');
  	return -1;
  }
  $iLastModifiedTime = filemtime($sTemplatefile);
    
  // send the specified status code
  if (404 == $aTemplate['status_code'])
  	header('HTTP/1.0 404 Not Found');   
  	
  /* figure out if we got a header.*/
  $aAdditionalHeaders = array();
  if (file_exists($sTemplateDir . '/' . $aTemplate['template'] . '/headers.txt')) {
  	$sHeaders = file_get_contents($sTemplateDir . '/' . $aTemplate['template'] . '/headers.txt');
  	foreach (preg_split('/[\x0d\x0a]+/', $sHeaders) as $sHeader)
  		$aAdditionalHeaders[] = $sHeader;  		  	    
  } 
  if (preg_match('/^(.*?)[\x0d\x0a]+####HEADEREND[\x0d\x0a]+(.*)$/sD', $sTemplate, $aMatch)) {
  	logdata("INFO: we got a header",9);
  	$sHeaders = $aMatch[1];
  	$sTemplate = $aMatch[2];

  	foreach (preg_split('/[\x0d\x0a]+/', $sHeaders) as $sHeader)
  		$aAdditionalHeaders[] = $sHeader;  		  	  
  }  
	/* now iterrate through headers
	 * we could send the header in one piece. but this may interfere with 
   * some protection schemes like suhosin that do not allow CR-LF in the header
	 * this will also make sure we do actuall send the LF and not just the CR */
  foreach ($aAdditionalHeaders as $sHeader)
  	header($sHeader, true);
  
  // (try to) filter out from the template: javascripts and SSIs
  // this is far from perfect, since javascript can hide in many places
  // issue warnings is such thing is found
  $sSanitizedBody = preg_replace(
  		array(
  			'/<script.*?<\/script>/is',
  			'/<script.*?\/\s*>/is',
  			'/<!--#.*?>/s',  		
  		),
  		array(
  			'',
  			'',
  			'',
  		),
  		$sTemplate  		  		
  	);
  if ($sSanitizedBody != $sTemplate) {
 	  //it would be more efficient to use the count parameter from preg_replace,
 	  //but it is only available in PHP5
  	logdata("WARN: removed risky content from templatefile \"$sTemplatefile\"",2);
  	$sTemplate = $sSanitizedBody;   
  }
  
  logdata("INFO: sending body", 9);
  $bDeliverBody = true;
  if ($iLastModifiedTime) { 
  	header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $iLastModifiedTime) . ' GMT');
  	if (@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $iLastModifiedTime) {
  		logdata("INFO: body not modified", 9);
  		header('HTTP/1.1 304 Not Modified');
  		$bDeliverBody = false; 
  	}  	
  }  
  
  if ($bDeliverBody) {  
  	header('Content-Length: ' . strlen($sTemplate));
  	print $sTemplate;
  }
  
  return $aTemplate['template']; 
}

function regex_validator($errno, $errstr, $errfile, $errline) {
	global $bRxErrorOccurred;	
	$bRxErrorOccurred = true;
	return true;
}

/**
 * read configuration file.
 */
function read_config($sFilename, $sRulePrefix) {
  global $aConfig;
  global $aTemplateLookup;
  global $bRxErrorOccurred;
  $aTemplateLookup[$sRulePrefix] = array();
  if (!file_exists($sFilename)) return;
  
  $hFile=fopen($sFilename, 'r');
  if (!$hFile) die_on_error("Failed to open configuration file \"$sFilename\"!");
  
  // specifies the section ([config], [rules], etc) we are in
  $currentSection = null;
  while ( $hFile && !feof($hFile) ) {
  	  $line=rtrim(fgets($hFile,4096));
  	  // skip comments
  	  if (preg_match('/^\s*[#;].*$/',$line)) 
  	  	continue;
  	  // new section
  	  if (preg_match('/^\s*\[(\w+)\]/i', $line, $matches)) {
  	  	$currentSection = strtolower($matches[1]);
  	  	continue;
  	  }
  	  // configuration settings - written to $aConfig
  	  if ('config' == $currentSection && preg_match('/^\s*(\w+)\s*=\s*(.*)/', $line, $aMatches)) {
  	  	$aConfig[$aMatches[1]] = $aMatches[2];
  	  	logdata("DEBUG: config parameter {$aMatches[1]} {$aMatches[2]}",9);
  	  	continue;
  	  }
  	  // rules - written to $aTemplateLookup - in the correct section
  	  if ('rules' == $currentSection && preg_match('/^\s*(.*?)\s*(\d+)(\.\d+)?$/', $line, $aMatches)) {
  	  	$rx = $aMatches[1];
  	  	if (!preg_match('/^\/.*\/[a-z]*/i', $rx))
  	  		$rx = '/' . preg_quote($rx) . '/i';
  	  	$template_id = intval($aMatches[2]);
  	  	$status_code = (isset($aMatches[3])) 
  	  		? intval(substr($aMatches[3], 1)) 
  	  		: 200;
  	  		
  	  	// test if the regex compiles
  	  	$bRxErrorOccurred = false;
  	  	set_error_handler('regex_validator');
  	  	preg_match($rx, '');
  	  	restore_error_handler();
  	  	
  	  	if ($bRxErrorOccurred) {
  	  		logdata("Invalid regex \"$rx\" found at line \"$line\"!",1);
  	  		continue;
  	  	}

  	  	$aTemplateLookup[$sRulePrefix][] = array(
  	  		'rx' => $rx,
  	  		'template' => $template_id,
  	  		'status_code' => $status_code,  	  	
  	  	);
  	  	logdata("DEBUG: template lookup {$aMatches[1]} {$aMatches[2]}",9);  	  	
  	  	continue;
  	  }
  	  if ('basenames' == $currentSection && preg_match('/^\s*(\d+)\s*=\s*(.*)/', $line, $aMatches)) {
  	  	$aConfig['template_basenames'][$aMatches[1]] = $aMatches[2]; 
  	  }
  	  
  }
}

/**
 * collect_request
 * 
 *   no parameters. Returns the full request (with body)
 */

function collect_request() {
  // assemble method, url and protocol
  $sRequest=$_SERVER['REMOTE_ADDR']."\n";
  $sRequest.=$_SERVER['REQUEST_METHOD'] . ' '
  	. (isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : $_SERVER['QUERY_STRING']) 
  	. ' ' . $_SERVER['SERVER_PROTOCOL']."\n";
  // get all headers
  foreach ( $_SERVER as $sKey=>$sHeader) {
    if ( preg_match("/^HTTP_/",$sKey) ) {
      $sRequest.=preg_replace("/^HTTP_/","",$sKey).": $sHeader\n";
    }
  }
  // get body
  $sBody=@file_get_contents('php://input');
  // only append body to request if there is actually one.
  if ( strlen($sBody) > 0 ) {
    $sRequest=$sRequest."\n\n".$sBody;
  }
  // return the request.
  return $sRequest;
}

function write_to_socket($hSocket, $sData) {
	while (strlen($sData) > 0) {
		$iWritten = fwrite($hSocket, $sData);
		if (false === $iWritten) return false;
		$sData = substr($sData, $iWritten);
	}
	return true;
}

/**
 * Sends a post request to the URL determined by the parameter. Supports only HTTP
 * currently (no HTTPS). $sPostData must contain pairs of urlencoded name=value
 * joined by &
 * The following variables are added automatically:
 * Returns the response on success, false on failure.
 *   - username
 *   - nonce
 *   - password (the hash of the password)
 *   - version (the version of index.php) 
 * @param $sUrl
 * @param $sPostData
 * @return string or false
 */
function send_post_request($sUrl, $sPostData) {
	global $whVersion;
	global $aConfig;
	
	if (!preg_match('/([a-z]+):\/\/(.*?)(\/.*)/i', $sUrl, $aMatches)) return false;
	$sProtocol = strtolower($aMatches[1]);
	if ('http' != $sProtocol) return false;
	$sHost     = $aMatches[2];
	$iPort     = 80;
	$sFile     = $aMatches[3];
	if (preg_match('/^(.*):(\d+)$/D', $sHost, $aMatches)) {
		$sHost = $aMatches[1];
		$iPort = intval($aMatches[2]);
	}
	
  $sNonce = get_nonce();  
  $sPass  = sha1($aConfig['password'].$aConfig['userid']);
  $sPass  = sha1($sNonce.$sPass);
  
  if ('' != $sPostData) $sPostData .= '&';
  $sPostData .= 'username=' . urlencode($aConfig['userid'])
  	. '&nonce=' . urlencode($sNonce)
  	. '&password=' . urlencode($sPass)
  	. '&version=' . urlencode($whVersion)
  	. "\x0d\x0a";	
	
	$fp = fsockopen($sHost, $iPort, $errno, $errstr, 30);
	if (!$fp) {
		logdata("ERROR: failed to connect to host $sHost:$iPort: $errstr ($errno)", 1);
		return false;
	}
	
	// use HTTP 1.0 to avoid getting chunked encoding which would require us to do more work :-)
	$sRequest = "POST $sFile HTTP/1.0\x0d\x0a"
		. "Host: $sHost\x0d\x0a"
		. "Pragma: no-cache\x0d\x0a"
		. "Accept: */*\x0d\x0a"
		. "User-Agent: hp-honeypot, ver $whVersion\x0d\x0a"
		. "Connection: Close\x0d\x0a"
		. "Content-Length: " . strlen($sPostData) . "\x0d\x0a"
		. "Content-Type: application/x-www-form-urlencoded\x0d\x0a"
		. "\x0d\x0a";	
	if (!write_to_socket($fp, $sRequest)) {
		fclose($fp);
		return false;
	}	
	if (!write_to_socket($fp, $sPostData)) {
		fclose($fp);
		return false;
	}
	
	$bDataReceived = false; $bFirstLine = true; $bEmptyLineRead = false;
	$sResult = '';
	while (!feof($fp)) {
		$sData = fgets($fp, 1024); 
		if ($bFirstLine) {
			$bDataReceived = preg_match('/200\s+OK/m', $sData);
			$bFirstLine = false;
		}
		if ($bEmptyLineRead) {
			// we are past the headers - store data if the headers said 200 OK
			if ($bDataReceived) $sResult .= $sData; 
		}
		else {
			if ('' == trim($sData)) $bEmptyLineRead = true;
		}				
	}
  fclose($fp);  	
  
  if (!$bDataReceived) return false;
  return $sResult;
}

/**
 * post_result
 */
function post_result($sRequest) {
	global $aConfig;

	if (!send_post_request($aConfig['url'], 'request=' . urlencode($sRequest))) {
		logdata("ERROR: Failed to send requests to server!");
		return false;
	}
  
	return true;
}

function die_on_error($sErrorMsg) {
	logdata("FATAL: $sErrorMsg", 1);
	if ('cli' == php_sapi_name()) {	
		echo "The following error was encountered:\n$sErrorMsg\n";
	}
	else {
		echo '<font color="red"><b>The following error was encountered:' . "<br><pre>\n"
			. htmlentities($sErrorMsg) . '</pre></b></font>';
	}	
	exit;
}

/**
 * Checks if the given path is above the document root.
 */
function is_above_document_root($sPath) {
	$sDocRoot = @$_SERVER['DOCUMENT_ROOT'];
	if (!is_dir($sDocRoot)) {
		if ('cli' == php_sapi_name())
			// on the command line we don't know where the DocumentRoot is
			// so there is no way we could check it...
			return true;
		else
			return false;
	}
	return is_above_directory($sPath, $sDocRoot);
}

function is_above_directory($sPath, $sBase) {
	$sPath = realpath($sPath);
	$sBase = realpath($sBase);
	return 0 !== strncmp($sPath, $sBase, strlen($sBase));
}

function is_below_directory($sPath, $sBase) {
	return !is_above_directory($sPath, $sBase);
}

/**
 * Checks if a given file is in the given directory (doesn't try to
 * "escape" using path traversal)
 * @param $file
 * @param $dir
 * @return unknown_type
 */
function is_file_in_directory($sFileName, $sDirName) {
	$sFileName = realpath(dirname($sFileName));
	$sDirName  = realpath($sDirName);
	return $sFileName == $sDirName;
}

/**
 * Checks if a given file is writeable 
 * @return boolean
 */
function is_file_writable($sFileName) {
	$fh = @fopen($sFileName, 'a');
	$result = ($fh) ? true : false;
	@fclose($fh);
	return $result;
}

/**
 * Tries to aquire the specified lockfile. Non-blocking.
 * Returns true on success, false on failure (in which case you probably stop
 * doing whatever you were doing..)
 * @param $sLockfileName
 * @return boolean
 */
function aquire_lock($sLockfileName) {
	$fh = fopen($sLockfileName, 'w+');
	if (!$fh) return false;
	return flock($fh, LOCK_EX|LOCK_NB);
}

/**
 * Receives a partial filename and checks wheather we should be able to "update" it
 * Returns a string with the fully qualified file name if yes, false otherwise 
 * @param $sFilename
 * @return string or false
 */
function validate_filename_for_update($sFilename) {
	global $sBaseDir;
	global $sTemplateDir;

	// config.txt is the only file we are allowed to update from the base dir
	if ('config.txt' == $sFilename)
		return $sBaseDir . '/config.txt';
	// all the others must be in the templates subdirectory
	// and furthermore, in a templates/\d+ directory
	// unfortuantely we can't use realpath here because it doesn't work for non-existent files...
	if (!preg_match("/^templates[\\/\\\\]\d+(?:[\\/\\\\]|$)/D", $sFilename)) return false;
	if (preg_match("/\\.\\./", $sFilename)) return false;
	$sFilename = $sBaseDir . '/' . $sFilename;

	return $sFilename;
}

/**
 * Deletes files and directories. Tries extra-hard to delete them :-)
 * @param $sFilename
 * @return boolean
 */
function rmdir_recursive($sFilename) {
	if (is_dir($sFilename)) {
		$sFilename = rtrim($sFilename, "\\/");
		foreach(glob($sFilename . '/{,.}*', GLOB_BRACE) as $sFile) {
			$sFile = basename($sFile);			
			if ('.' == $sFile || '..' == $sFile) continue;
			if (!rmdir_recursive($sFilename . '/' . $sFile))
				return false;
		}

		if (@rmdir($sFilename)) {
			return true;
		}
		else {
			// try some extreme measures - this may leave the FS in an inconsistent state!
			// (wrong permissions mainly)
			@chmod($sFilename, 0644);
			return @rmdir($sFilename);			
		}
	}
	elseif (file_exists($sFilename)) {
		if (@unlink($sFilename)) {
			return true;
		}
		else {
			// try some extreme measures - this may leave the FS in an inconsistent state!
			// (wrong permissions mainly)
			@chmod($sFilename, 0644);
			return @unlink($sFilename);
		}
	}
	else
		return true;			
}

/**
 * Extracts a line from a concatenated input (what the update function sends). Warning!
 * Truncates the input so that the extracted line is removed
 * @param $sConcatedInput
 * @return string
 */
function extract_line(&$sConcatedInput) {
	if ('' == $sConcatedInput)
		return null;
	
	$iSpacePos = strpos($sConcatedInput, ' ');
	if (false === $iSpacePos)
		die_on_error('Failed to extract line! Space not found!');
	$iStrLen = intval(substr($sConcatedInput, 0, $iSpacePos), 16);
	if (0 == $iStrLen || $iStrLen > (strlen($sConcatedInput)-1))
		die_on_error('Failed to extract line! Invalid length!');
	$sFileLine = substr($sConcatedInput, $iSpacePos+1, $iStrLen);			 
	$sConcatedInput = substr($sConcatedInput, $iSpacePos+$iStrLen+1);
	return $sFileLine;		
}

function ob_no_output($sBuffer) {
	return '';
}

function get_nonce($bNewNonce = true) {
	global $sUniqueNonce;
	if ($bNewNonce)
		$sUniqueNonce = uniqid(rand(), true); 
	return $sUniqueNonce;
}
